'''

    Windows system event callbacks

    A thread is used to pump the messages.
    Once Init() is called, this thread will pump until the
    interpreter stops.
    
'''

import threading
import pyHook
import win32com.client
import pythoncom


_DEBUG = False
_THE_THREAD = None
_THE_SHELL = None
_RELEASE_CALLBACKS = []
_PRESS_CALLBACKS = []
_ENABLED = True

_PRESSED = set([]) # For stupid disabling of key repeat

def DEBUG(b):
    global _DEBUG
    _DEBUG = b

class PumpingThread(threading.Thread):
    def __init__(self, on_key_press, on_key_release):
        threading.Thread.__init__(self)
        self.setDaemon(True) # Let the GI kill me and end.
        self._on_key_press = on_key_press
        self._on_key_release = on_key_release
        
    def run(self):
        # create the hook mananger
        hm = pyHook.HookManager()
        
        # register two callbacks
        hm.KeyDown = self._on_key_press
        hm.KeyUp = self._on_key_release
        
        # hook into the mouse and keyboard events
        hm.HookKeyboard()
        
        # Pump forever
        pythoncom.PumpMessages()

def _on_key_press(event):
    global _ENABLED
    if not _ENABLED:
        if _DEBUG:
            print "Not filtering press", event.Key
        return True

    global _PRESSED
    key = event.Key
    if key in _PRESSED:
        if _DEBUG:
            print "Skipping key repeat", event.Key
        return False # Repeat keys must be swallowed
    _PRESSED.add(key)
    return _on(_PRESS_CALLBACKS, key)
    
def _on_key_release(event):
    global _ENABLED
    if not _ENABLED:
        if _DEBUG:
            print "Not filtering release", event.Key
        return True

    global _PRESSED
    key = event.Key
    _PRESSED.remove(key)
    return _on(_RELEASE_CALLBACKS, key)

def _on(callbacks, arg):
    global _DEBUG
    if _DEBUG:
        print "ON KEY:", arg
    for cb in callbacks:
        ret = cb(arg)
        if _DEBUG:
            print "  ", ret, "returned by", cb
        if ret:
            # Stop propagating when
            # someone requested it:
            if _DEBUG:
                print "  Swallowed"
            return False
    if _DEBUG:
        print "   Event send to system."
    return True
    
def emit(keys):
    '''
    key must be like:
        "a"
        "+a"
        "^a"
        "%a"
        "^+a"
        "{DELETE}"
        "{TAB}"
        ...
    '''
    global _ENABLED, _THE_SHELL
    enabled_restore = _ENABLED
    if _DEBUG:
        print "Emitting Keys", repr(keys), "to system"
    _ENABLED = False
    _THE_SHELL.SendKeys(keys)
    _ENABLED = enabled_restore

def add_key_press(func):
    '''
    Add a function to call on each key press.
    The func must accept one argument: the event keycode.
    '''
    global _PRESS_CALLBACKS
    _PRESS_CALLBACKS.append(func)
    
def add_key_release(func):
    '''
    Add a function to call on each key release.
    The func must accept one argument: the event keycode.
    '''
    global _RELEASE_CALLBACKS
    _RELEASE_CALLBACKS.append(func)

def init():
    global _THE_THREAD
    if _THE_THREAD is not None:
        raise Exception("Cannot Init another sys event filter.")
    global _THE_SHELL
    _THE_SHELL = win32com.client.Dispatch("WScript.Shell")
    _THE_THREAD = PumpingThread(_on_key_press, _on_key_release)
    _THE_THREAD.start()

def enable(b):
    global _ENABLED
    _ENABLED = b and True or False
    if _DEBUG:
        print "SYSEVENT ENABLED", _ENABLED
